#-------------------------------------------------------------------------------
# Name:         nmap.py
# Purpose:      Replicates limited nmap functionality using python
# Author:       phillipsme
# Created:      12/08/2014
# Copyright:    (c) phillipsme 2014
# Licence:      Free to use, free to have fun!
# Version:      beta!!! (0.2)
# ToDo:         add threading
#-------------------------------------------------------------------------------
import socket
import argparse
import sys
import time

def main():
    # Output command line args to screen
    if args.verbose: printmsg("Arguments used:"); print args ;

    starttime=time.time()
    # Start Scanning
    results={}
    for target in targets:
        results[target]= portscan(target,ports,args.tcpscan,args.udpscan,args.verbose)
    printmsg(("Total scantime %.2f seconds") % (time.time()-starttime))

    for target in results:
        print "%s TCP:%s  UDP:%s" % (target,results[target][0],results[target][1])
    return results

def portscan(target,ports,tcp,udp,verbose):
    #target=IPaddr,ports=list of ports,tcp=true/false,udp=true/false,verbose=true/false
    tcpports=[]
    udpports=[]
    targetstarttime=time.time()
    if tcp:
        for portnum in ports:
            try:
                s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                s.settimeout(0.01)
                s.connect((target, portnum))
            except Exception:
                failvar = 0
                if verbose: print "%d/tcp \tclosed" % (portnum)
            else:
                if verbose: print "%d/tcp \topen"% (portnum)
                tcpports.append(portnum)
            s.close()
    if udp:
        for portnum in ports:
            try:
                s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                s.settimeout(0.1)
                s.sendto("--TEST LINE--", (target, portnum))
                recv, svr = s.recvfrom(255)
            except Exception, e:
                try: errno, errtxt = e
                except ValueError:
                    if verbose: print "%d/udp \topen"% (portnum)
                    udpports.append(portnum)
                else:
                    if verbose: print "%d/udp \tclosed" % (portnum)
            s.close()
    printmsg(("Scanned %s in %.2f seconds - Open: %iTCP, %iUDP" % \
                (target,time.time()-targetstarttime,len(tcpports),len(udpports))))
    return tcpports, udpports

def errormsg(msg): print "[!] Error: %s" % (msg) ; sys.exit(1)
def printmsg(msg): print "[+] nmap.py: %s" % (msg)

def iprange(addressrange): # converts a ip range into a list
    list=[]
    first3octets = '.'.join(addressrange.split('-')[0].split('.')[:3]) + '.'
    for i in range(int(addressrange.split('-')[0].split('.')[3]),int(addressrange.split('-')[1])+1):
        list.append(first3octets+str(i))
    return list

def ip2bin(ip):
    b = ""
    inQuads = ip.split(".")
    outQuads = 4
    for q in inQuads:
        if q != "": b += dec2bin(int(q),8); outQuads -= 1
    while outQuads > 0: b += "00000000"; outQuads -= 1
    return b

def dec2bin(n,d=None):
    s = ""
    while n>0:
        if n&1: s = "1"+s
        else: s = "0"+s
        n >>= 1
    if d is not None:
        while len(s)<d: s = "0"+s
    if s == "": s = "0"
    return s

def bin2ip(b):
    ip = ""
    for i in range(0,len(b),8):
        ip += str(int(b[i:i+8],2))+"."
    return ip[:-1]

def returnCIDR(c):
    parts = c.split("/")
    baseIP = ip2bin(parts[0])
    subnet = int(parts[1])
    ips=[]
    if subnet == 32: return bin2ip(baseIP)
    else:
        ipPrefix = baseIP[:-(32-subnet)]
        for i in range(2**(32-subnet)): ips.append(bin2ip(ipPrefix+dec2bin(i, (32-subnet))))
        return ips

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='nmap.py - Replicates limited nmap functionality in python')
    parser.add_argument('-v', '--verbose', action='store_true', help='Enable this for full output')
    parser.add_argument('-sS', '--tcpscan', action='store_true', help='Enable this for TCP scans')
    parser.add_argument('-sU', '--udpscan', action='store_true', help='Enable this for UDP scans')
    parser.add_argument('-p', '--ports', default='1-1024', help='The ports you want to scan (21,22,80,135-139,443,445)')
    parser.add_argument('-t', '--targets', help='The target(s) you want to scan (192.168.0.1)')
    if len(sys.argv)==1: parser.print_help(); sys.exit(0)
    args = parser.parse_args()

    # Set target (and convert for FQDN)
    targets=[]
    if args.targets:
        if '/' in args.targets: #found cidr target
            targets = returnCIDR(args.targets)
        elif '-' in args.targets:
            targets = iprange(args.targets)
        else:
            try: targets.append(socket.gethostbyname(args.targets)) # get IP from FQDN
            except: errormsg("Failed to translate hostname to IP address")
    else: parser.print_help(); errormsg("You need to set a hostname")

    # Set ports
    if args.ports == '-': args.ports = '1-65535'
    ranges = (x.split("-") for x in args.ports.split(","))
    ports = [i for r in ranges for i in range(int(r[0]), int(r[-1]) + 1)]

    main()
